package communicate;

import intro.ThreadUtil;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;

/**
 * Java 中的 wait & notify 机制
 * 用于线程间的通信解耦
 * 解决及时性和开销问题
 * 
 * 提炼出等待通知的框架
 * 等待方:
 * 1. 获得锁
 * 2. 如果条件不满足, 调用 wait()
 * 3. 条件满足执行相应动作
 * 
 * 通知方
 * 1. 获得锁
 * 2. 修改条件
 * 3. 唤醒所有等待线程
 * 
 * @author insight
 * @since 2021/8/30
 */
public class WaitNotifyTest {
    static boolean flag = true;
    static final Object LOCK = new Object();
    static SimpleDateFormat sdf = new SimpleDateFormat("HH:mm::ss");
    
    public static void main(String[] args) {
        /*
         * 流程:
         * 启动 WaitThread 调用 wait() 释放锁 进入等待队列
         * 经过 2s
         * 启动 NotifyThread 调用 notifyAll() 唤醒等待队列里的所有线程
         * 由于 NotifyThread 继续持有锁 WaitThread 无法执行代码
         * NotifyThread 执行完毕释放锁 WaitThread 获得锁 执行代码
         */
        new Thread(new Wait(), "WaitThread").start();
        ThreadUtil.second(2);
        new Thread(new Notify(), "NotifyThread").start();
    }

    static class Wait implements Runnable {
        @Override
        public void run() {
            synchronized (LOCK) {
                while (flag) {
                    System.out.println(Thread.currentThread() + "flag is true, wait @ "
                            + LocalDateTime.now().getSecond());
                    try {
                        // 进入等待队列并释放锁
                        LOCK.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread() + "flag is false, running @ "
                        + LocalDateTime.now().getSecond());
            }
        }
    }

    static class Notify implements Runnable {
        @Override
        public void run() {
            synchronized (LOCK) {
                System.out.println(Thread.currentThread() + "hold lock, notify @ "
                        + LocalDateTime.now().getSecond());
                flag = false;
                // notify 之后不会马上获得锁 只是从等待队列移动到同步队列而已
                LOCK.notifyAll();
                // 唤醒后 NotifyThread 还在同步代码块 未释放
                // 所以 WaitThread 阻塞在同步队列
                ThreadUtil.second(3);
            }
            // 尝试移动延时语句 果然唤醒后就获得锁 从而执行代码
            // ThreadUtil.second(3);
            synchronized (LOCK) {
                System.out.println(Thread.currentThread() + "hold lock again, sleep @ "
                        + LocalDateTime.now().getSecond());
                ThreadUtil.second(3);
            }
        }
    }
}